/*	$Id: vhost.c 1119 2010-12-12 21:29:18Z phrakt $	*/
/*
 * Copyright (c) 2003 Jean-Francois Brousseau <phrakt@gmail.com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
 * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL  DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <sys/types.h>
#include <sys/socket.h>

#include <netinet/in.h>
#include <arpa/inet.h>

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <canopy/log.h>
#include <canopy/mem.h>
#include <canopy/thread.h>
#include <canopy/string.h>

#include "msg.h"
#include "http.h"
#include "vhost.h"
#include "private.h"




/*
 * http_vhost_alloc()
 *
 * Allocate a new HTTP virtual host structure of type <type> with name <name>.
 * The <addr>
 *
 * Returns a pointer to the new virtual host structure on success, or NULL
 * on failure.
 */
http_vhost_t*
http_vhost_alloc(const char *name, const char *rootdir,
    enum http_vhost_type type, const void *addr)
{
	http_vhost_t *host;

	if ((host = cnp_malloc(sizeof(*host))) == NULL) {
		http_log_errno("failed to allocate virtual host");
		return (NULL);
	}
	memset(host, 0, sizeof(*host));

	host->vh_type = type;
	host->vh_refcnt = 1;

	if (cnp_rwlock_init(&host->vh_rwlock, NULL) == -1) {
		http_log_err("failed to initialize virtual host lock");
		cnp_free(host);
		return (NULL);
	}

	if ((host->vh_name = cnp_strdup(name)) == NULL) {
		http_log_errno("failed to allocate virtual host name");
		http_vhost_free(host);
		return (NULL);
	}

	if ((rootdir != NULL) && (http_vhost_setdir(host, rootdir) == -1)) {
		http_vhost_free(host);
		return (NULL);
	}

	http_vhost_setaddr(host, addr);

	return (host);
}

/*
 * http_vhost_ref()
 *
 * Returns a reference to the virtual host <host>.
 */
http_vhost_t*
http_vhost_ref(http_vhost_t *host)
{
	if (cnp_rwlock_wrlock(&host->vh_rwlock) == -1) {
		http_log_err("failed to lock host");
		return (NULL);
	}

	host->vh_refcnt++;

	(void)cnp_rwlock_unlock(&host->vh_rwlock);

	return (host);
}

/*
 * http_vhost_free()
 *
 * Free the HTTP virtual host structure <host> and all of its associated
 * resources.
 */
void
http_vhost_free(http_vhost_t *host)
{
	int count;

	if (cnp_rwlock_wrlock(&host->vh_rwlock) == -1) {
		http_log_err("failed to lock host");
		return;
	}

	count = --host->vh_refcnt;

	(void)cnp_rwlock_unlock(&host->vh_rwlock);

	if (count == 0) {
		if (host->vh_name != NULL)
			cnp_free(host->vh_name);

		if ((host->vh_type == HTTP_VHOST_NAME) &&
		    (host->vh_addr.vh_hostname != NULL))
			cnp_free(host->vh_addr.vh_hostname);

		if (host->vh_docroot != NULL)
			cnp_free(host->vh_docroot);

		(void)cnp_rwlock_destroy(&host->vh_rwlock);
		cnp_free(host);
	}
}

/*
 * http_vhost_getaddr()
 *
 * Get the address associated with the virtual host <host> and store it
 * into the destination buffer <dst>, which is of size <len>.  If <host> is
 * of type HTTP_VHOST_NAME, <dst> should point to a char buffer, and if the
 * host is of type HTTP_VHOST_ADDR, <dst> should point to an http_sockaddr_t
 * variable;
 *
 * Returns 0 on success, or -1 on failure.
 */
int
http_vhost_getaddr(http_vhost_t *host, void *dst, size_t *len)
{
	int ret = 0;

	if (cnp_rwlock_rdlock(&host->vh_rwlock) == -1) {
		http_log_err("failed to lock host");
		return (-1);
	}

	switch (host->vh_type) {
	case HTTP_VHOST_NAME:
		strlcpy(dst, host->vh_addr.vh_hostname, *len);
		break;
	case HTTP_VHOST_ADDR:
		if (*len < sizeof(host->vh_addr.vh_addr))
			ret = -1;
		else {
			memcpy(dst, &host->vh_addr,
			    sizeof(host->vh_addr.vh_addr));
			*len = sizeof(host->vh_addr.vh_addr);
		}
		break;
	default:
		ret = -1;
	}

	(void)cnp_rwlock_unlock(&host->vh_rwlock);

	return (ret);
}

/*
 * http_vhost_getdir()
 *
 * Get the path to the root directory associated with the virtual host <host>
 * and store it into the destination buffer <dst>, which is of size <len>.
 * The string stored in <dst> is guaranteed to be NUL-terminated.
 *
 * Returns 0 on success, or -1 on failure.
 */
int
http_vhost_getdir(http_vhost_t *host, char *dst, size_t len)
{
	if (cnp_rwlock_rdlock(&host->vh_rwlock) == -1) {
		http_log_err("failed to lock host");
		return (-1);
	}

	strlcpy(dst, host->vh_docroot, len);

	(void)cnp_rwlock_unlock(&host->vh_rwlock);

	return (0);
}

/*
 * http_vhost_getname()
 *
 * Get the name string associated with the virtual host <host> and store it
 * into the destination buffer <dst>, which is of size <len>.  The string
 * stored in <dst> is guaranteed to be NUL-terminated.
 *
 * Returns a pointer to the destination buffer on success, or NULL on failure.
 */
char*
http_vhost_getname(http_vhost_t *host, char *dst, size_t len)
{
	if (cnp_rwlock_rdlock(&host->vh_rwlock) == -1) {
		http_log_err("failed to lock host");
		return (NULL);
	}

	strlcpy(dst, host->vh_name, len);

	(void)cnp_rwlock_unlock(&host->vh_rwlock);

	return (dst);
}

/*
 * http_vhost_gettype()
 *
 *
 * Returns 0 on success, or -1 on failure.
 */
enum http_vhost_type
http_vhost_gettype(http_vhost_t *host)
{
	return (host->vh_type);
}

/*
 * http_vhost_setaddr()
 *
 * Set the address associated with the virtual host <host> to the value
 * specified in <addr>.  If <host> is of type HTTP_VHOST_NAME, <addr> should
 * point to a NUL-terminated string specifying the new hostname, and if the
 * host is of type HTTP_VHOST_ADDR, <addr> should point to an http_sockaddr_t
 * structure.
 *
 * Returns 0 on success, or -1 on failure.
 */
int
http_vhost_setaddr(http_vhost_t *host, const void *addr)
{
	int ret = 0;
	char *hostname = NULL;

	if ((host->vh_type == HTTP_VHOST_NAME) &&
	   ((hostname = cnp_strdup((const char *)addr)) == NULL)) {
		http_log_errno("failed to copy hostname");
		return (-1);
	}

	if (cnp_rwlock_wrlock(&host->vh_rwlock) == -1) {
		http_log_err("failed to lock host");
		cnp_free(host);
		return (-1);
	}

	switch (host->vh_type) {
	case HTTP_VHOST_NAME:
		if (host->vh_addr.vh_hostname != NULL)
			cnp_free(host->vh_addr.vh_hostname);
		host->vh_addr.vh_hostname = hostname;
		break;
	case HTTP_VHOST_ADDR:
		memcpy(&host->vh_addr, addr, sizeof(host->vh_addr.vh_addr));
		break;
	default:
		ret = -1;
		break;
	}

	(void)cnp_rwlock_unlock(&host->vh_rwlock);

	return (ret);
}

/*
 * http_vhost_setdir()
 *
 * Set the root document directory associated with the virtual host <host> to
 * the path specified in <rootdir>.
 *
 * Returns 0 on success, or -1 on failure.
 */
int
http_vhost_setdir(http_vhost_t *host, const char *rootdir)
{
	size_t len;
	char buf[CANOPY_PATH_MAX], *copy;

	if ((len = strlcpy(buf, rootdir, sizeof(buf))) >= sizeof(buf)) {
		http_log_err("directory path is too long");
		return (-1);
	}

	while (buf[len - 1] == '/')
		buf[--len] = '\0';

	if ((copy = cnp_strdup(buf)) == NULL) {
		http_log_errno("failed to copy root directory path");
		return (-1);
	}

	if (cnp_rwlock_wrlock(&host->vh_rwlock) == -1) {
		http_log_err("failed to lock host");
		cnp_free(copy);
		return (-1);
	}

	if (host->vh_docroot != NULL)
		cnp_free(host->vh_docroot);

	host->vh_docroot = copy;
	http_log_debug("vhost %s changed document root to %s",
	    host->vh_name, host->vh_docroot);

	(void)cnp_rwlock_unlock(&host->vh_rwlock);

	return (0);
}

/*
 * http_vhost_setname()
 *
 * Set the name of the virtual host <host> to the value of <name>.  The name
 * of a virtual host can not be longer than HTTP_VHOST_MAXNAMELEN (including
 * the NUL byte).
 *
 * Returns 0 on success, or -1 on failure.
 */
int
http_vhost_setname(http_vhost_t *host, const char *name)
{
	size_t len;
	char *copy;

	if ((len = strlen(name)) >= HTTP_VHOST_MAXNAMELEN) {
		http_log_err("virtual host name is too long");
		return (-1);
	}

	if ((copy = cnp_strdup(name)) == NULL) {
		http_log_errno("failed to copy virtual host name");
		return (-1);
	}

	if (cnp_rwlock_wrlock(&host->vh_rwlock) == -1) {
		http_log_err("failed to lock host");
		cnp_free(copy);
		return (-1);
	}

	if (host->vh_name != NULL)
		cnp_free(host->vh_name);

	host->vh_name = copy;
	http_log_debug("vhost %s changed document root to %s",
	    host->vh_name, host->vh_docroot);

	(void)cnp_rwlock_unlock(&host->vh_rwlock);

	return (0);
}

/*
 * http_vhost_mkpath()
 *
 * Build an absolute path from the Virtual Host <vhost>'s root directory and
 * the path relative <path>.  The resulting path is stored in the destination
 * buffer <dst>, which is of size <len>.
 *
 * Returns 0 on success, or -1 on failure.
 */
int
http_vhost_mkpath(http_vhost_t *host, const char *path, char *dst, size_t len)
{
	int ret;

	if (cnp_rwlock_rdlock(&host->vh_rwlock) == -1) {
		http_log_err("failed to lock host");
		return (-1);
	}

	ret = snprintf(dst, len, "%s/%s", host->vh_docroot, path);
	if ((ret == -1) || (ret >= (int)len)) {
		http_log_err("failed to build vhost path");
		ret = -1;
	}
	else
		ret = 0;

	(void)cnp_rwlock_unlock(&host->vh_rwlock);

	return (ret);
}
